import threading
from threading import Condition, RLock, Thread, Semaphore, current_thread
import logging
from .app import get_dt
from bisect import bisect_left
from util.web.session import ErrException


class Meeting:
    def __init__(self):
        self.registered = []
        self.lock = RLock()
        self.attenders = {}

    def register(self, attender):
        self.registered.append(attender)

    def attend(self, attender):
        with self.lock:
            if attender in self.attenders:
                return False, False
            else:
                self.attenders.add(attender)
                return True, len(self.registered) == len(self.attenders)

    def reset(self):
        with self.lock:
            self.attenders.clear()


class Collector:
    def __init__(self, senders=None, keep_waiting_func=None):
        self.keep_waiting_func = keep_waiting_func

        lock = RLock()
        self._waiting = Condition(lock)
        self._senders = [] if senders is None else [e for e in senders]
        self._objs = None

    def add_sender(self, sender):
        self._senders.append(sender)

    def get_senders(self):
        return self._senders.copy()

    def __contains__(self, sender):
        return sender in self._senders

    def keep_waiting(self, sender):
        if self.keep_waiting_func is None:
            return True
        return self.keep_waiting_func(sender)

    def notify_all(self):
        with self._waiting:
            self._waiting.notify_all()

    def prepare_send(self, sender):
        """
        准备发送数据。如果没有准备好，当前线程会阻塞直到已经准备好
        :param sender 准备发送数据的发送者
        :return True表示sender是完整数据的第一个发送者，False表示它不是
        """
        with self._waiting:
            while self.keep_waiting(sender) and (self._objs is not None and sender in self._objs):
                self._waiting.wait()
            if self._objs is None:
                self._objs = {}
                return True
        return False

    def send(self, sender, obj):
        """
        发送数据。如果sender上一次发送的数据尚未被处理，则会等待直到它被处理完毕。每次调用这个方法前必须先调用prepare_send()。
        @return None表示发送成功，但是其他发送者还没有把数据发送全。否则返回一个字典则表示所有相关发送者发送的数据。
        """
        with self._waiting:
            self._objs[sender] = obj
            if len(self._objs) == len(self._senders):
                objs = self._objs
                self._objs = None
                self._waiting.notify_all()
            else:
                return None
        return objs


class ThreadPool:
    def __init__(self, size):
        self.size = size
        self.threads = []
        self.occupied = []
        self.params = []
        self.lock = RLock()
        self.has_call = []
        self.has_result = []
        self.stopping = False
        self.started = False

    def call_asynch(self, func, *args, **kwargs):
        with self.lock:
            if not self.started:
                raise Exception('The ThreadPool is not started, please call start()')
            found = False
            for i, occu in enumerate(self.occupied):
                if not occu:
                    found = True
                    break

            if not found:
                i = self.size
                self._append_thread(i)
                self.size += 1
                self.threads[i].start()
                logging.getLogger().info('%d threads are used.' % self.size)
            self.occupied[i] = True
            self.params[i] = (func, args, kwargs)
            self.has_call[i].notify_all()

    def start(self):
        if self.started:
            logging.getLogger().info('Thread pool was already started')
            return
        logging.getLogger().info('start thread pool')
        with self.lock:
            self.started = True
            self.stopping = False
            for i in range(self.size):
                self._append_thread(i)
            for p in self.threads:
                p.start()
        logging.getLogger().info('Thread pool is started')

    def _append_thread(self, index):
        self.threads.append(Thread(target=self.run, args=(index,), daemon=True))
        self.occupied.append(False)
        self.params.append(None)
        self.has_call.append(Condition(self.lock))
        self.has_result.append(Condition(self.lock))

    def stop(self):
        with self.lock:
            self.stopping = True
            for c in self.has_call:
                c.notify_all()

        for p in self.threads:
            if p is not current_thread():
                p.join()

        with self.lock:
            self.threads.clear()
            self.occupied.clear()
            self.params.clear()
            self.has_call.clear()
            self.has_result.clear()
            self.started = False

    def run(self, index):
        while not self.stopping:
            with self.lock:
                while self.params[index] is None:
                    if self.stopping:
                        return
                    self.has_call[index].wait()
                func, args, kwargs = self.params[index]
                self.params[index] = None
            try:
                self.silent_invoke(func, args, kwargs)
            finally:
                with self.lock:
                    if index < len(self.occupied):
                        self.occupied[index] = False
                        self.has_result[index].notify_all()

    def silent_invoke(self, func, args, kwargs):
        try:
            func(*args, **kwargs)
        except Exception as e:
            logging.getLogger().error('%s: %s' % (get_dt(), e), exc_info=e)


class Priority:
    def __init__(self, resources: int):
        """
        :param resources 资源的总数量
        """
        self._resources = resources
        self._waitings = []
        self._lock = RLock()

    def has_resources(self):
        with self._lock:
            return self._resources > 0

    def can_acquire(self, priority: int):
        with self._lock:
            if self._resources > 0 or len(self._waitings) == 0:
                return True
            return priority <= self._waitings[0].priority

    def acquire(self, priority: int, blocking=True, timeout=None):
        with self._lock:
            if self._resources > 0:
                self._resources -= 1
                return True
            if not blocking:
                return False

            item = _Item(priority)
            pos = bisect_left(self._waitings, item)
            if pos < len(self._waitings):
                current = self._waitings[pos]
                if current.priority == priority:
                    current.count += 1
                    item = current
                else:
                    self._waitings.insert(pos, item)
            else:
                self._waitings.append(item)
            if item.condition is None:
                item.condition = Condition(self._lock)

            while self._resources <= 0:
                got_it = item.condition.wait(timeout=timeout)
                if timeout is not None and not got_it:
                    break

            got_it = self._resources > 0
            if got_it:
                self._resources -= 1

            item.count -= 1
            if item.count == 0:
                pos = self._waitings.index(item)
                del self._waitings[pos]
            return got_it

    def release(self):
        with self._lock:
            self._resources += 1
            if len(self._waitings) > 0:
                self._waitings[0].condition.notify_all()


class _Item:
    def __init__(self, priority: int):
        self.priority = priority
        self.condition = None
        self.count = 1

    def __eq__(self, other):
        return self.priority == other.priority

    def __lt__(self, other):
        return self.priority < other.priority

    def __gt__(self, other):
        return self.priority > other.priority

    def __le__(self, other):
        return self.priority <= other.priority

    def __ge__(self, other):
        return self.priority >= other.priority


